--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit 8e47de38ad638ff32faba96642422de95e696e3e
Parents : 81b574d
Author : Sudo-Ivan <ivan@quad4.io>
Signature : Signature validation error
Date : 2026-01-16T08:53:11-06:00
Add LXMF link handling and improve link rendering logic
- Implemented event handling for LXMF links in ConversationViewer and RNCPPage components to navigate to message destinations.
- Updated LinkUtils to support both NomadNet and LXMF link formats, enhancing link detection and rendering.
- Modified RNPathTracePage to display error state alongside loading and result states for better user feedback.
- Improved utility functions to handle edge cases for zero or negative values in various formatting methods.
Changes
5 files changed, 55 insertions(+), 15 deletions(-)
Diff
diff --git a/meshchatx/src/frontend/components/messages/ConversationViewer.vue b/meshchatx/src/frontend/components/messages/ConversationViewer.vue
index 69118745..2dd13bff 100644
--- a/meshchatx/src/frontend/components/messages/ConversationViewer.vue
+++ b/meshchatx/src/frontend/components/messages/ConversationViewer.vue
@@ -2061,6 +2061,18 @@ export default {
});
}
}
+
+ const lxmfLink = event.target.closest(".lxmf-link");
+ if (lxmfLink) {
+ event.preventDefault();
+ const address = lxmfLink.getAttribute("data-lxmf-address");
+ if (address) {
+ this.$router.push({
+ name: "messages",
+ params: { destinationHash: address },
+ });
+ }
+ }
},
async updatePropagationNodeStatus() {
try {
diff --git a/meshchatx/src/frontend/components/rncp/RNCPPage.vue b/meshchatx/src/frontend/components/rncp/RNCPPage.vue
index 43532fec..0c3d7b4e 100644
--- a/meshchatx/src/frontend/components/rncp/RNCPPage.vue
+++ b/meshchatx/src/frontend/components/rncp/RNCPPage.vue
@@ -541,6 +541,18 @@ export default {
});
}
}
+
+ const lxmfLink = event.target.closest(".lxmf-link");
+ if (lxmfLink) {
+ event.preventDefault();
+ const address = lxmfLink.getAttribute("data-lxmf-address");
+ if (address) {
+ this.$router.push({
+ name: "messages",
+ params: { destinationHash: address },
+ });
+ }
+ }
},
},
};
diff --git a/meshchatx/src/frontend/components/tools/RNPathTracePage.vue b/meshchatx/src/frontend/components/tools/RNPathTracePage.vue
index 78fdf806..46c4fcdf 100644
--- a/meshchatx/src/frontend/components/tools/RNPathTracePage.vue
+++ b/meshchatx/src/frontend/components/tools/RNPathTracePage.vue
@@ -69,7 +69,7 @@
</div>
<!-- results area -->
- <div v-if="traceResult || isLoading" class="space-y-6">
+ <div v-if="traceResult || isLoading || error" class="space-y-6">
<!-- loading state -->
<div v-if="isLoading" class="glass-card p-12 flex flex-col items-center justify-center gap-4">
<div class="relative">
diff --git a/meshchatx/src/frontend/js/LinkUtils.js b/meshchatx/src/frontend/js/LinkUtils.js
index c19a80c8..4d939e8a 100644
--- a/meshchatx/src/frontend/js/LinkUtils.js
+++ b/meshchatx/src/frontend/js/LinkUtils.js
@@ -1,19 +1,35 @@
export default class LinkUtils {
/**
- * Detects and wraps NomadNet links in HTML.
- * Supports nomadnet://<hash>:/path and <hash>:/path
+ * Detects and wraps Reticulum (NomadNet and LXMF) links in HTML.
+ * Supports nomadnet://<hash>, nomadnet@<hash>, lxmf://<hash>, lxmf@<hash> and bare <hash>
*/
- static renderNomadNetLinks(text) {
+ static renderReticulumLinks(text) {
if (!text) return "";
- // Hash is 32 hex chars. Path is optional.
+ // Hash is 32 hex chars. Path is optional (NomadNet only).
const hashPattern = "[a-fA-F0-9]{32}";
- const nomadnetRegex = new RegExp(`(?:nomadnet://)?(${hashPattern})(?::(/[\\w\\d./?%&=-]*))?`, "g");
+ // This matches optional prefixes (nomadnet://, nomadnet@, lxmf://, lxmf@)
+ // followed by a 32-char hex hash, and an optional path starting with :
+ const reticulumRegex = new RegExp(
+ `(nomadnet://|nomadnet@|lxmf://|lxmf@)?(${hashPattern})(?::(/[\\w\\d./?%&=-]*))?`,
+ "g"
+ );
- return text.replace(nomadnetRegex, (match, hash, path) => {
- const fullPath = path || "/page/index.mu";
- const url = `${hash}:${fullPath}`;
- return `<a href="#" class="nomadnet-link text-blue-600 dark:text-blue-400 hover:underline font-mono" data-nomadnet-url="${url}">${match}</a>`;
+ return text.replace(reticulumRegex, (match, prefix, hash, path) => {
+ // Determine if it should be treated as a NomadNet link:
+ // - Has nomadnet prefix
+ // - OR has a path component (e.g. hash:/page)
+ const isNomadNet =
+ (prefix && (prefix.startsWith("nomadnet://") || prefix.startsWith("nomadnet@"))) || !!path;
+
+ if (isNomadNet) {
+ const fullPath = path || "/page/index.mu";
+ const url = `${hash}:${fullPath}`;
+ return `<a href="#" class="nomadnet-link text-blue-600 dark:text-blue-400 hover:underline font-mono" data-nomadnet-url="${url}">${match}</a>`;
+ } else {
+ // Treat as LXMF link
+ return `<a href="#" class="lxmf-link text-blue-600 dark:text-blue-400 hover:underline font-mono" data-lxmf-address="${hash}">${match}</a>`;
+ }
});
}
@@ -35,7 +51,7 @@ export default class LinkUtils {
*/
static renderAllLinks(text) {
text = this.renderStandardLinks(text);
- text = this.renderNomadNetLinks(text);
+ text = this.renderReticulumLinks(text);
return text;
}
}
diff --git a/meshchatx/src/frontend/js/Utils.js b/meshchatx/src/frontend/js/Utils.js
index 9dfb99c2..aa5adf8d 100644
--- a/meshchatx/src/frontend/js/Utils.js
+++ b/meshchatx/src/frontend/js/Utils.js
@@ -9,7 +9,7 @@ class Utils {
}
static formatBytes(bytes) {
- if (bytes === 0) {
+ if (!bytes || bytes <= 0) {
return "0 Bytes";
}
@@ -127,7 +127,7 @@ class Utils {
}
static formatBitsPerSecond(bits) {
- if (bits === 0) {
+ if (!bits || bits <= 0) {
return "0 bps";
}
@@ -141,7 +141,7 @@ class Utils {
}
static formatBytesPerSecond(bytesPerSecond) {
- if (bytesPerSecond === 0 || bytesPerSecond == null) {
+ if (!bytesPerSecond || bytesPerSecond <= 0) {
return "0 B/s";
}
@@ -155,7 +155,7 @@ class Utils {
}
static formatFrequency(hz) {
- if (hz === 0 || hz == null) {
+ if (!hz || hz <= 0) {
return "0 Hz";
}
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────